package com.zlcx.tz.live.common.interceptor;

import com.alibaba.druid.support.http.WebStatFilter;
import com.zlcx.tz.live.common.annotation.AutoLog;
import com.zlcx.tz.live.common.config.WebAppConfigurer;
import com.zlcx.tz.live.common.thread.MdcRunable;
import com.zlcx.tz.live.exception.SendExceptionUtil;
import com.zlcx.tz.live.exception.TimeOutException;
import com.zlcx.tz.live.service.impl.ApiRequestStatisticsServiceImpl;
import com.zlcx.tz.live.web.BaseController;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.log4j.MDC;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static com.zlcx.tz.live.common.annotation.AutoLog.Level.*;

/**
 * Controller log切面
 *
 * @author gaoyanfeng
 * @author anjl
 **/
@Slf4j
@Aspect
@Component
@Order(1)
public class AutoLogAspect {
    public static final String REQUEST_ID = "requestId";
    public static final String APP = "app";

    @Around("@annotation(autoLog)")
    public Object systemWebLogMonitor(ProceedingJoinPoint joinPoint, AutoLog autoLog) throws Throwable {

        //获取参数类型
        Object result;
        Object[] params = joinPoint.getArgs();
        String username = getUser(),
                logString = getMethodInfo(joinPoint);
        String requestId = null, url = "", headers = "";
        StopWatch stopWatch = new StopWatch();

        //requestId用于日志追踪使用
        HttpServletRequest request = null;
        if (Objects.nonNull(RequestContextHolder.getRequestAttributes())) {
            request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            //获取request
            headers = getHeaders(request);
            //请求url
            url = request.getRequestURI();
            requestId = request.getParameter(REQUEST_ID);
            MdcRunable.setAppIfAbsent(BaseController.getApp(request));
        }
        MdcRunable.setRequestIdIfAbsent(requestId);

        //接口请求日志
        if (autoLog.logLevel() == DEBUG) {
            log.debug(logString + "：url = {} params = {} headers = {} user = {}", url, formatParam(params).toString(), headers, username);
        } else if (autoLog.logLevel() == INFO) {
            log.info(logString + "：url = {} params = {} headers = {} user = {}", url, formatParam(params).toString(), headers, username);
        }

        stopWatch.start("start");
        result = joinPoint.proceed();
        stopWatch.stop();

        //接口响应日志
        if (autoLog.logLevel() == DEBUG) {
            log.debug(logString + "响应结果：url = {} result = {} 耗时 = {}", url, result, stopWatch.getTotalTimeMillis());
        } else if (autoLog.logLevel() == INFO) {
            log.info(logString + "响应结果：url = {} result = {} 耗时 = {}", url, result, stopWatch.getTotalTimeMillis());
        }

        //接口如果超时，邮件报警
        if (stopWatch.getTotalTimeMillis() > WebAppConfigurer.timeout) {
            WebAppConfigurer.WebTimeOutEnum webTimeOutEnum = WebAppConfigurer.WebTimeOutEnum.url(url);
            if (!StringUtils.isEmpty(webTimeOutEnum) && stopWatch.getTotalTimeMillis() > webTimeOutEnum.getTimeout()) {
                sendExceptionUtil.sendException(logString + " 接口耗时超" + webTimeOutEnum.getTimeout() + "毫秒： \n耗时 = " + stopWatch.getTotalTimeMillis() + "\nurl = " + url + "\nparams = " + formatParam(params).toString(), new TimeOutException());
            }
            if (StringUtils.isEmpty(webTimeOutEnum) && stopWatch.getTotalTimeMillis() > 5000 * 20) {
                sendExceptionUtil.sendException(logString + " 接口耗时超" + WebAppConfigurer.timeout + "毫秒： \n耗时 = " + stopWatch.getTotalTimeMillis() + "\nurl = " + url + "\nparams = " + formatParam(params).toString(), new TimeOutException());
            }
        }

        if (Objects.nonNull(RequestContextHolder.getRequestAttributes())) {
            HttpServletRequest finalRequest = request;
            String finalUrl = url;
            commonTaskExecutor.execute(() -> apiRequestStatisticsService.record(finalRequest.getMethod(), finalUrl, stopWatch.getTotalTimeMillis()));
        }

        MDC.clear();
        return result;
    }

    public static String getHeaders(HttpServletRequest request) {
        Enumeration<String> enumeration = request.getHeaderNames();
        StringBuilder headers = new StringBuilder();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getHeader(name);
            headers.append(name).append(":").append(value).append(",");
        }
        return headers.toString();
    }

    private List<Object> formatParam(Object[] params) {
        return Arrays.stream(params).filter(obj -> !(obj instanceof WebStatFilter.StatHttpServletResponseWrapper)).collect(Collectors.toList());
    }

    private String getMethodInfo(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        if (targetMethod.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation apiOperation = targetMethod.getAnnotation(ApiOperation.class);
            return apiOperation.value();
        }
        return "";
    }

    private String getUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (Objects.isNull(authentication)) {
            return null;
        }

        Object object = authentication.getPrincipal();
        String username = null;
        if (object instanceof UserDetails) {
            username = ((UserDetails) object).getUsername();
        }
        return username;
    }

    @Autowired
    private SendExceptionUtil sendExceptionUtil;
    @Autowired
    ApiRequestStatisticsServiceImpl apiRequestStatisticsService;
    @Resource(name = "commonTaskExecutor")
    private AsyncTaskExecutor commonTaskExecutor;
}

